package dedup

import (
	"fmt"
	"sync"
	"time"
)

// MemoryBlockIndex implements an in-memory block index
type MemoryBlockIndex struct {
	blocks      map[string]*BlockMetadata
	mu          sync.RWMutex
	stats       BlockStats
}

// NewMemoryBlockIndex creates a new in-memory block index
func NewMemoryBlockIndex() (*MemoryBlockIndex, error) {
	return &MemoryBlockIndex{
		blocks: make(map[string]*BlockMetadata),
	}, nil
}

// AddBlock adds a block to the index
func (idx *MemoryBlockIndex) AddBlock(block BlockMetadata) error {
	idx.mu.Lock()
	defer idx.mu.Unlock()

	if _, exists := idx.blocks[block.Hash]; exists {
		return fmt.Errorf("block %s already exists", block.Hash)
	}

	idx.blocks[block.Hash] = &block
	idx.updateStats(block.Size, true)

	return nil
}

// GetBlock retrieves a block from the index
func (idx *MemoryBlockIndex) GetBlock(hash string) (*BlockMetadata, error) {
	idx.mu.RLock()
	defer idx.mu.RUnlock()

	block, exists := idx.blocks[hash]
	if !exists {
		return nil, fmt.Errorf("block %s not found", hash)
	}

	return block, nil
}

// UpdateBlock updates a block in the index
func (idx *MemoryBlockIndex) UpdateBlock(block BlockMetadata) error {
	idx.mu.Lock()
	defer idx.mu.Unlock()

	existing, exists := idx.blocks[block.Hash]
	if !exists {
		return fmt.Errorf("block %s not found", block.Hash)
	}

	// Update stats if size changed
	if existing.Size != block.Size {
		idx.updateStats(existing.Size, false) // Remove old size
		idx.updateStats(block.Size, true)     // Add new size
	}

	idx.blocks[block.Hash] = &block
	return nil
}

// DeleteBlock deletes a block from the index
func (idx *MemoryBlockIndex) DeleteBlock(hash string) error {
	idx.mu.Lock()
	defer idx.mu.Unlock()

	block, exists := idx.blocks[hash]
	if !exists {
		return fmt.Errorf("block %s not found", hash)
	}

	idx.updateStats(block.Size, false)
	delete(idx.blocks, hash)

	return nil
}

// FindSimilarBlocks finds blocks similar to the given hash
func (idx *MemoryBlockIndex) FindSimilarBlocks(hash string, threshold float64) ([]BlockMetadata, error) {
	idx.mu.RLock()
	defer idx.mu.RUnlock()

	var similarBlocks []BlockMetadata

	// For now, implement a simple similarity check based on hash prefix
	// In a real implementation, this would use more sophisticated similarity algorithms
	for _, block := range idx.blocks {
		if block.Hash == hash {
			continue // Skip the block itself
		}

		// Simple similarity: check if hashes share common prefix
		similarity := calculateHashSimilarity(hash, block.Hash)
		if similarity >= threshold {
			similarBlocks = append(similarBlocks, *block)
		}
	}

	return similarBlocks, nil
}

// GetBlockStats returns block statistics
func (idx *MemoryBlockIndex) GetBlockStats() BlockStats {
	idx.mu.RLock()
	defer idx.mu.RUnlock()

	return idx.stats
}

// updateStats updates the block statistics
func (idx *MemoryBlockIndex) updateStats(size int64, add bool) {
	if add {
		idx.stats.TotalBlocks++
		idx.stats.TotalSize += size
		idx.stats.AverageSize = float64(idx.stats.TotalSize) / float64(idx.stats.TotalBlocks)

		// Update unique blocks and duplicate rate
		idx.stats.UniqueBlocks = int64(len(idx.blocks))
		if idx.stats.TotalBlocks > 0 {
			idx.stats.DuplicateRate = 1.0 - float64(idx.stats.UniqueBlocks)/float64(idx.stats.TotalBlocks)
		}
	} else {
		idx.stats.TotalBlocks--
		idx.stats.TotalSize -= size
		if idx.stats.TotalBlocks > 0 {
			idx.stats.AverageSize = float64(idx.stats.TotalSize) / float64(idx.stats.TotalBlocks)
		} else {
			idx.stats.AverageSize = 0
		}

		// Update unique blocks and duplicate rate
		idx.stats.UniqueBlocks = int64(len(idx.blocks))
		if idx.stats.TotalBlocks > 0 {
			idx.stats.DuplicateRate = 1.0 - float64(idx.stats.UniqueBlocks)/float64(idx.stats.TotalBlocks)
		}
	}
}

// FindDuplicates finds duplicate blocks in the index
func (idx *MemoryBlockIndex) FindDuplicates(minSize int64) ([]DuplicateBlockGroup, error) {
	idx.mu.RLock()
	defer idx.mu.RUnlock()

	// Group blocks by size first (performance optimization)
	sizeGroups := make(map[int64][]*BlockMetadata)
	for _, block := range idx.blocks {
		if block.Size >= minSize {
			sizeGroups[block.Size] = append(sizeGroups[block.Size], block)
		}
	}

	var duplicateGroups []DuplicateBlockGroup

	// For each size group, find actual duplicates by comparing hashes
	for size, blocks := range sizeGroups {
		if len(blocks) <= 1 {
			continue // No duplicates possible
		}

		// Group by hash
		hashGroups := make(map[string][]*BlockMetadata)
		for _, block := range blocks {
			hashGroups[block.Hash] = append(hashGroups[block.Hash], block)
		}

		// Create duplicate groups
		for _, group := range hashGroups {
			if len(group) > 1 {
				var hashes []string
				var totalRefs int64
				for _, block := range group {
					hashes = append(hashes, block.Hash)
					totalRefs += block.ReferenceCount
				}

				duplicateGroups = append(duplicateGroups, DuplicateBlockGroup{
					Hashes:    hashes,
					Size:      size,
					Count:     len(group),
					Frequency: totalRefs,
				})
			}
		}
	}

	return duplicateGroups, nil
}

// GetAllBlocks returns all blocks in the index
func (idx *MemoryBlockIndex) GetAllBlocks() ([]BlockMetadata, error) {
	idx.mu.RLock()
	defer idx.mu.RUnlock()

	blocks := make([]BlockMetadata, 0, len(idx.blocks))
	for _, block := range idx.blocks {
		blocks = append(blocks, *block)
	}

	return blocks, nil
}

// GetBlocksBySize returns blocks within a size range
func (idx *MemoryBlockIndex) GetBlocksBySize(minSize, maxSize int64) ([]BlockMetadata, error) {
	idx.mu.RLock()
	defer idx.mu.RUnlock()

	var blocks []BlockMetadata
	for _, block := range idx.blocks {
		if block.Size >= minSize && block.Size <= maxSize {
			blocks = append(blocks, *block)
		}
	}

	return blocks, nil
}

// GetLeastRecentlyUsed returns the least recently used blocks
func (idx *MemoryBlockIndex) GetLeastRecentlyUsed(limit int) ([]BlockMetadata, error) {
	idx.mu.RLock()
	defer idx.mu.RUnlock()

	// Convert map to slice for sorting
	blocks := make([]BlockMetadata, 0, len(idx.blocks))
	for _, block := range idx.blocks {
		blocks = append(blocks, *block)
	}

	// Sort by last access time (ascending)
	for i := 0; i < len(blocks)-1; i++ {
		for j := i + 1; j < len(blocks); j++ {
			if blocks[i].LastAccess > blocks[j].LastAccess {
				blocks[i], blocks[j] = blocks[j], blocks[i]
			}
		}
	}

	// Return the least recently used blocks
	if limit > len(blocks) {
		limit = len(blocks)
	}

	return blocks[:limit], nil
}

// calculateHashSimilarity calculates similarity between two hashes
func calculateHashSimilarity(hash1, hash2 string) float64 {
	if len(hash1) != len(hash2) {
		return 0.0
	}

	similarity := 0.0
	for i := 0; i < len(hash1) && i < len(hash2); i++ {
		if hash1[i] == hash2[i] {
			similarity += 1.0
		}
	}

	return similarity / float64(len(hash1))
}

// Cleanup removes stale entries and optimizes the index
func (idx *MemoryBlockIndex) Cleanup() error {
	idx.mu.Lock()
	defer idx.mu.Unlock()

	currentTime := time.Now().Unix()
	staleThreshold := int64(24 * 60 * 60) // 24 hours

	// Remove blocks that haven't been accessed in a long time and have no references
	for hash, block := range idx.blocks {
		if block.ReferenceCount == 0 && (currentTime-block.LastAccess) > staleThreshold {
			delete(idx.blocks, hash)
			idx.updateStats(block.Size, false)
		}
	}

	return nil
}